package com.bsj.power.pls.queue;

import com.alibaba.fastjson.JSON;
import com.bsj.consumerQ.function.HandleRunT;
import com.bsj.consumerQ.method.QueueAndRunT;
import com.bsj.power.common.config.cached.*;
import com.bsj.power.common.config.redis.RedisConstant;
import com.bsj.power.common.config.rocketmq.producer.BaseMqPushUtil;
import com.bsj.power.common.constant.MqTopicAndTagConstants;
import com.bsj.power.common.constant.NumericalConstants;
import com.bsj.power.common.def.dto.cardReport.BindCardDTO;
import com.bsj.power.common.def.entity.alarmReport.AlarmReport;
import com.bsj.power.common.def.entity.job.WorkPersonnel;
import com.bsj.power.common.def.entity.locationDevice.LocationDevice;
import com.bsj.power.common.def.entity.path.Path;
import com.bsj.power.common.def.entity.path.PathPoint;
import com.bsj.power.common.def.entity.personnel.Personnel;
import com.bsj.power.common.def.vo.WebSocketMsgVO;
import com.bsj.power.common.def.vo.pls.httpVo.HttpTagPosVO;
import com.bsj.power.common.mapper.AlarmReportMapper;
import com.bsj.power.common.util.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.sql.Time;
import java.util.Arrays;
import java.util.Date;
import java.util.List;

/**
 * @author LiJiaXin
 * @version 1.0.0
 * @description WorkAreaAlarmQueue
 * @time 2024/8/15 15:37
 */
@Slf4j
@Component
public class PathAlarmQueue extends QueueAndRunT<HttpTagPosVO> {

    @Resource
    private AlarmReportMapper alarmReportMapper;

    @Autowired
    private RedisCached redisCached;

    @Autowired
    private PathCached pathCached;

    @Autowired
    private BindCardCached bindCardCached;

    @Autowired
    private PersonnelCached personnelCached;

    @Autowired
    private WorkPersonnelCached workPersonnelCached;

    @Autowired
    private LocationDeviceCached locationDeviceCached;

    @Autowired
    private PathAlarmCached pathAlarmCached;

    @Autowired
    private BaseMqPushUtil baseMqPushUtil;

    public void start() {
        start("pathAlarmQueue", new HandleRunT<HttpTagPosVO>() {
            @Override
            public void doRun(List<HttpTagPosVO> list) {
                for (HttpTagPosVO httpTagPosVO : list) {
                    String deviceName = httpTagPosVO.getSn();
                    LocationDevice locationDevice = locationDeviceCached.hgetLocationDeviceInfo(deviceName);
                    if (locationDevice == null) {
                        continue;
                    }
                    //绑卡信息
                    BindCardDTO bindCardDTO = bindCardCached.hgetBindCardInfo(RedisConstant.BIND_CARD_REDIS_DEVICE_NAME_KEY + deviceName);
                    if (bindCardDTO == null) {
                        log.info("本次报警未绑卡{}", deviceName);
                        continue;
                    }
                    //负责人员信息
                    Personnel personnel = personnelCached.hgetPersonnelInfo(bindCardDTO.getEmpNo());
                    //作业人员信息
                    WorkPersonnel workPersonnel = workPersonnelCached.getWorkPersonnelInfo(bindCardDTO.getEmpNo());
                    if (personnel == null && workPersonnel == null) {
                        continue;
                    }
                    long time = httpTagPosVO.getTime();
                    Date date = new Date(time);
                    //设备当前位置
                    double[] pos = httpTagPosVO.getPos();
                    List<Path> paths = pathCached.hgetAllPathInfo();
//                    log.info("围栏信息打印******：{}", paths);
//                    log.info("获取所有的围栏：" + JSON.toJSONString(paths.size()));
                    //可能会出现多个围栏相交并且时间条件也满足的情况，所以即使已经产生一次报警也不结束循环
                    for (Path path : paths) {
                        AlarmReport alarmReport = new AlarmReport();
                        //标识是否需要更新结束时间
                        boolean flag = false;
                        //是否产生报警
                        boolean isAlarm = false;
                        //是否是重复报警
                        boolean isRepetitionAlarm = false;
                        //1进围栏 2出围栏
                        Integer status = path.getStatus();
                        Integer pathId = path.getPathId();
                        String pathName = path.getPathName();
                        List<PathPoint> pathPoints = path.getPathPoints();
//                        if (path.getPathName().equals("添加围栏测试") || path.getPathId() == 40) {
//                            log.info("围栏名称：{}", path.getPathName());
//                            log.info("围栏各个点：{}, 当前位置：{}", pathPoints, pos);
//                        }
                        AlarmReport alarmCached = pathAlarmCached.hgetPathAlarmInfo(deviceName, pathId);
                        //首先判断时间
                        boolean comparison = timeCompare(date, path.getBeginTime(), path.getEndTime());
                        //时间条件触发
                        if (comparison) {
                            //true为在围栏内，反之不在围栏内
                            boolean inRange = isInRange(pathPoints, pos);
                            //围栏范围内的情况
                            if (inRange) {
                                //产生进围栏报警
                                if (status.equals(NumericalConstants.ONE)) {
                                    if (alarmCached != null) {
                                        Integer alarmType = alarmCached.getAlarmType();
                                        //上次报警后围栏状态改变的情况：删除缓存并修改表中结束时间，上次为出围栏报警
                                        if (!alarmType.equals(status)) {
                                            flag = true;
                                            pathAlarmCached.hdelPathAlarmInfo(deviceName, pathId);
                                            //产生新的报警
                                            isAlarm = true;
                                            alarmReport.setAlarmType(NumericalConstants.ONE);
                                        } else {
                                            //重复报警
                                            isRepetitionAlarm = true;
                                        }
                                    } else {
                                        //产生新的报警
                                        isAlarm = true;
                                        alarmReport.setAlarmType(NumericalConstants.ONE);
                                    }
                                }
                            } else {
                                //产生出围栏报警
                                if (status.equals(NumericalConstants.TWO)) {
                                    if (alarmCached != null) {
                                        Integer alarmType = alarmCached.getAlarmType();
                                        //上次报警后围栏状态改变的情况：删除缓存并修改表中结束时间，上次为进围栏报警
                                        if (!alarmType.equals(status)) {
                                            flag = true;
                                            pathAlarmCached.hdelPathAlarmInfo(deviceName, pathId);
                                            //产生新的报警
                                            isAlarm = true;
                                            alarmReport.setAlarmType(NumericalConstants.TWO);
                                        } else {
                                            //重复报警
                                            isRepetitionAlarm = true;
                                        }
                                    } else {
                                        //产生新的报警
                                        isAlarm = true;
                                        alarmReport.setAlarmType(NumericalConstants.TWO);
                                    }
                                }
                            }
                            if (isAlarm) {
                                alarmReport.setBeginTime(date);
                                alarmReport.setHandleState(NumericalConstants.ONE);
                                alarmReport.setPos(Arrays.toString(pos));
                                //设备信息
                                alarmReport.setDeviceName(deviceName);
                                alarmReport.setNikeName(locationDevice.getNikeName());
                                alarmReport.setDeviceType(locationDevice.getDeviceType());
                                //人员信息
                                if (personnel != null) {
                                    alarmReport.setPersonnelId(personnel.getPersonnelId());
                                    alarmReport.setPersonnelName(personnel.getName());
                                } else if (workPersonnel != null) {
                                    alarmReport.setPersonnelId(workPersonnel.getWorkPersonnelId());
                                    alarmReport.setPersonnelName(workPersonnel.getName());
                                }
                                //围栏信息
                                alarmReport.setPathId(pathId);
                                alarmReport.setPathName(pathName);
                                //摄像头id集
                                alarmReport.setCameraIds(pathCached.smemsPathDevice(pathId));
                                //写入前再判断一次，避免重复报警
                                if (pathAlarmCached.hgetPathAlarmInfo(deviceName, pathId) != null) {
                                    continue;
                                }
                                //TODO 报警附件处理
                                //添加报警数据
                                alarmReportMapper.insert(alarmReport);
                                //缓存报警信息
                                pathAlarmCached.hsetPathAlarmInfo(alarmReport);
                                //报警提醒
                                WebSocketMsgVO webSocketMsgVO = WebSocketMsgVO.builder()
                                        .msgType(NumericalConstants.TWO)
                                        .obj(alarmReport)
                                        .build();
                                baseMqPushUtil.sendTopicBytes(MqTopicAndTagConstants.SYSTEM_WEB_SERVER_CHANGE_TOPIC,
                                        MqTopicAndTagConstants.SYSTEM_WEB_PRODUCT_CHANGE_TAG, JSON.toJSONBytes(webSocketMsgVO));
//                                    alarmWebSocket.send(JSON.toJSONString(alarmReport));
                                log.info("产生报警{}", alarmReport);
                            } else {
                                //如果是重复报警不做处理，反之结束报警
                                if (alarmCached != null && !isRepetitionAlarm) {
                                    flag = true;
                                    pathAlarmCached.hdelPathAlarmInfo(deviceName, pathId);
                                }
                            }
                        } else {
                            //时间条件没触发（过期），如果有报警的情况下，结束报警
                            if (alarmCached != null) {
                                flag = true;
                                pathAlarmCached.hdelPathAlarmInfo(deviceName, pathId);
                            }
                        }
                        //插入结束时间
                        if (flag) {
                            log.info("结束报警：{}", alarmCached);
                            alarmReportMapper.updateById(new AlarmReport(alarmCached.getAlarmId(), date));
                        }
                    }
                }
            }
        });
    }

    /**
     * 是否在范围内（允许重合情况）
     * 对多边形的每一条边进行检查，判断点与边的相对位置，来确定射线法交点的数量
     * 如果交点的数量不为零且x与z在各自最大值与最小值范围内，则点在多边形内部
     * TODO 暂时只需要判断xy，不需要判断高度
     * TODO 围栏每个点的顺序是xyz，高度是y z代替了原来的y
     * TODO 真实位置数据推送过来的顺序xyz,高度是z，且需要取y的负值
     *
     * @param pathPoints
     * @param pos
     * @return boolean
     * @author ljx
     * @time 2024/5/21 22:21
     */
    public static boolean isInRange(List<PathPoint> pathPoints, double[] pos) {
        double x = pos[0];
        //取y的负值
        double y = -pos[1];
        double z = pos[2];
        double tempX = pathPoints.get(0).getX();
        double tempZ = pathPoints.get(0).getZ();
        double maxX = tempX;
        double minX = tempX;
        double maxZ = tempZ;
        double minZ = tempZ;
        int count = 0;
        int size = pathPoints.size();
        for (int i = 0; i < size; i++) {
            double x1 = pathPoints.get(i).getX();
            double y1 = pathPoints.get(i).getY();
            double z1 = pathPoints.get(i).getZ();
            //最大值
            maxX = x1 > maxX ? x1 : maxX;
            maxZ = z1 > maxZ ? z1 : maxZ;
            //最小值
            minX = x1 < minX ? x1 : minX;
            minZ = z1 < minZ ? z1 : minZ;
            //下一个点
            int index = (i + 1) % size;
            double x2 = pathPoints.get(index).getX();
            double y2 = pathPoints.get(index).getY();
            double z2 = pathPoints.get(index).getZ();

            boolean flag = x <= x2 && (x > x1 || Double.compare(x1, x2) < 0);
            // 当线段和垂直向下的射线相交时，count值改变
            if (y >= z1) {
                if (y <= z2 && flag) {
                    count++;
                }
            } else {
                if (y > z2 && flag) {
                    count--;
                }
            }
        }
        boolean xFlag = (x <= maxX && x >= minX);
        boolean yFlag = (y <= maxZ && y >= minZ);
        // TODO 高度判断暂定如下，后续可调整
//        boolean zFlag = (z <= maxZ && z >= minZ);
//        return count != 0 && xFlag && zFlag;

//        log.info("在围栏内：{}", count != 0 && xFlag);
        log.info("在围栏内：{}", count != 0);
//        return count != 0 && xFlag;
        return count != 0;
    }

//    /**
//     * 是否在范围内（允许重合情况）
//     * 对多边形的每一条边进行检查，判断点与边的相对位置，来确定射线法交点的数量
//     * 如果交点的数量不为零且x与z在各自最大值与最小值范围内，则点在多边形内部
//     * TODO 围栏每个点的顺序是xyz，真实位置数据推送过来的顺序？
//     *
//     * @param pathPoints
//     * @param pos
//     * @return boolean
//     * @author ljx
//     * @time 2024/5/21 22:21
//     */
//    public static boolean isInRange(List<PathPoint> pathPoints, double[] pos) {
//        double x = pos[0];
//        double y = pos[1];
//        double z = pos[2];
//        double tempX = pathPoints.getACDeviceStatusData(0).getX();
//        double tempZ = pathPoints.getACDeviceStatusData(0).getZ();
//        double maxX = tempX;
//        double minX = tempX;
//        double maxZ = tempZ;
//        double minZ = tempZ;
//        int count = 0;
//        int size = pathPoints.size();
//        for (int i = 0; i < size; i++) {
//            double x1 = pathPoints.getACDeviceStatusData(i).getX();
//            double y1 = pathPoints.getACDeviceStatusData(i).getY();
//            double z1 = pathPoints.getACDeviceStatusData(i).getZ();
//            //最大值
//            maxX = x1 > maxX ? x1 : maxX;
//            maxZ = z1 > maxZ ? z1 : maxZ;
//            //最小值
//            minX = x1 < minX ? x1 : minX;
//            minZ = z1 < minZ ? z1 : minZ;
//            //下一个点
//            int index = (i + 1) % size;
//            double x2 = pathPoints.getACDeviceStatusData(index).getX();
//            double y2 = pathPoints.getACDeviceStatusData(index).getY();
//
//            boolean flag = x <= x2 && (x > x1 || Double.compare(x1, x2) < 0);
//            // 当线段和垂直向下的射线相交时，count值改变
//            if (y >= y1) {
//                if (y <= y2 && flag) {
//                    count++;
//                }
//            } else {
//                if (y > y2 && flag) {
//                    count--;
//                }
//            }
//        }
//        boolean xFlag = (x <= maxX && x >= minX);
//        // TODO 高度判断暂定如下，后续可调整
////        boolean zFlag = (z <= maxZ && z >= minZ);
////        return count != 0 && xFlag && zFlag;
//
////        log.info("在围栏内：{}", count != 0 && xFlag);
//        log.info("在围栏内：{}", count != 0);
////        return count != 0 && xFlag;
//        return count != 0;
//    }

    public static void main(String[] args) {
        List<PathPoint> pathPoints = List.of(new PathPoint(-108.056774, 0.1, -96.211424), new PathPoint(-108.056774, 0.1, 25.240531),
                new PathPoint(-30.863844, 0.1, 25.240531), new PathPoint(-30.863844, 0.1, -96.211424));
        //需要判断的坐标点
//        double[] pos = {44.15, 48.450, 100.00};
        double[] pos = {-69.829947, -12.52999998, 6.53983};
//        double[] pos = {44.15, 48.450, 100.00};

        boolean inside = isInRange(pathPoints, pos);
        System.out.println("点是否在多边形内部: " + inside);
    }


    /**
     * 设置报警数据
     *
     * @param deviceName
     * @param alarmReport
     * @return void
     * @author ljx
     * @time 2024/5/21 21:03
     */
    private boolean setAlarmData(String deviceName, AlarmReport alarmReport) {
        boolean result = true;
        //绑卡信息
        BindCardDTO bindCardDTO = bindCardCached.hgetBindCardInfo(RedisConstant.BIND_CARD_REDIS_DEVICE_NAME_KEY + deviceName);
        //当前处于绑卡状态
        if (bindCardDTO != null) {
            //人员信息
            Personnel personnel = personnelCached.hgetPersonnelInfo(bindCardDTO.getEmpNo());
            if (personnel != null) {
                Integer personnelId = personnel.getPersonnelId();
                alarmReport.setPersonnelId(personnelId);
                alarmReport.setPersonnelName(personnel.getName());
//                // 确认当前作业
//                Map<String, String> jobMap = redisCached.hgetAll(RedisConstant.JOB_ORDER_RUN_KEY);
//                if (!CollectionUtils.isEmpty(jobMap)) {
//                    jobMap.entrySet().forEach(entrySet -> {
//                        Job job = JSON.parseObject(entrySet.getValue(), Job.class);
//                        if (job != null) {
//                            List<Integer> personnelIds = job.getPersonnel();
//                            if (!CollectionUtils.isEmpty(personnelIds) && personnelIds.contains(personnelId)) {
//                                jobIds.add(job.getJobId());
//                            }
//                        }
//                    });
//                }
            } else {
                result = false;
            }
        } else {
            log.info("本次报警未绑卡{}", deviceName);
            result = false;
        }
        return result;
    }

    /**
     * 时间比较
     * 在两个时间范围内返回true
     *
     * @param date
     * @param beginTime
     * @param endTime
     * @return boolean
     * @author ljx
     * @time 2024/5/21 19:23
     */
    private boolean timeCompare(Date date, Time beginTime, Time endTime) {
        Time time = Time.valueOf(DateUtils.dateToStr(DateUtils.TIME_STANDARD_FORMAT_HMS, date));
        return time.compareTo(beginTime) >= NumericalConstants.ZERO && time.compareTo(endTime) <= NumericalConstants.ZERO;
    }

}
